/************************************************************************
Copyright (c) 2010, 2013, C.R. All rights reserved. 

E-mail: crtoyou@gmail.com; crtoyou@163.com

You can redistribute it and/or modify it under the terms of the
GNU Lesser General Public License as published by the
Free Software Foundation version 3 of the License.

This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. if not, write to the Free Software Foundation, Inc., 
51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
************************************************************************/

#ifndef __CR_LOSHU_TRACKER_H__
#define __CR_LOSHU_TRACKER_H__

#include <afxext.h>
#include <crloshu.h>

class CLoshuTracker : public CRectTracker
{
public:
	// Style Flags
	enum StyleFlagsEx
	{
		//solidLine = 1, dottedLine = 2, hatchedBorder = 4,
		//resizeInside = 8, resizeOutside = 16, hatchInside = 32,
		solidMargin = 64, dottedMargin = 128
	};

	// Hit-Test codes
	enum MarginHit
	{
		mghtLeft = 0x10, 
		mghtRight = 0x20, 
		mghtTop = 0x40, 
		mghtBottom = 0x80, 
		mghtTopLeft = mghtTop | mghtLeft,
		mghtTopRight = mghtTop | mghtRight,
		mghtBottomLeft = mghtBottom | mghtLeft,
		mghtBottomRight = mghtBottom | mghtRight,
		mghtMask = 0xF0
	};

public:
	LOSHUMARGIN m_margin;
	BOOL m_bCanTrackRect;
	RECT m_boxLast[4];
	HPEN m_hDotPen;

public:
	CLoshuTracker()
		: CRectTracker()
		, m_hDotPen(NULL)
		, m_bCanTrackRect(TRUE)
	{
		ZeroMemory(&m_margin, sizeof(m_margin));
	}

	CLoshuTracker(LOSHUMARGIN* lpMargin, LPCRECT lpSrcRect, UINT nStyle) 
		: CRectTracker(lpSrcRect, nStyle)
		, m_hDotPen(NULL)
		, m_bCanTrackRect(TRUE)
	{
		ASSERT(lpMargin);
		m_margin = *lpMargin;
	}

	~CLoshuTracker(void)
	{
		if (m_hDotPen)
		{
			DeleteObject(m_hDotPen);
		}
	}

	void Draw(CDC* pDC)
	{
		ASSERT(IsValidLoshu(m_margin, m_rect.Width(), m_rect.Height()));
		CWnd* pWnd = pDC->GetWindow();
		pDC = pWnd->GetDC();
		CRectTracker::Draw(pDC);
		CPen* pOldPen;
		if (m_nStyle & dottedMargin)
		{
			if (!m_hDotPen)
				m_hDotPen = ::CreatePen(PS_DOT, 0, RGB(0,0,0));
			pOldPen = (CPen*)pDC->SelectObject(CPen::FromHandle(m_hDotPen));
		}
		else if (m_nStyle & solidMargin)
		{
			pOldPen = (CPen*)pDC->SelectStockObject(DC_PEN);
		}
		else
		{
			pDC->ReleaseAttribDC();
			return;
		}
		CRect rect;
		GetCenterRect(rect, m_margin, m_rect);
		//DrawMarginH(pDC, rect.top);
		pDC->MoveTo(m_rect.left, rect.top-1);
		pDC->LineTo(m_rect.right, rect.top-1);
		//DrawMarginH(pDC, rect.bottom);
		pDC->MoveTo(m_rect.left, rect.bottom);
		pDC->LineTo(m_rect.right, rect.bottom);
		//DrawMarginV(pDC, rect.left);
		pDC->MoveTo(rect.left-1, m_rect.top);
		pDC->LineTo(rect.left-1, m_rect.bottom);
		//DrawMarginV(pDC, rect.right);
		pDC->MoveTo(rect.right, m_rect.top);
		pDC->LineTo(rect.right, m_rect.bottom);
		pDC->SelectObject(pOldPen);
		pWnd->ReleaseDC(pDC);
	}

	virtual void AdjustRect(int nHandle, LPRECT lpRect)
	{
		CRectTracker::AdjustRect(nHandle, lpRect);
		RECT rect;
		GetCenterRect(rect, m_margin, m_rect);
		if ((unsigned)nHandle >= 8 || !::IsRectEmpty(&rect))
			return;
		//hitTopLeft = 0, hitTopRight = 1, hitBottomRight = 2, hitBottomLeft = 3,
		//hitTop = 4, hitRight = 5, hitBottom = 6, hitLeft = 7, hitMiddle = 8
		static const int nMargins[8] = {
			mghtTopLeft, mghtTopRight, mghtBottomRight, mghtBottomLeft, 
			mghtTop, mghtRight, mghtBottom, mghtLeft
		};
		int nMargin = nMargins[nHandle];
		if (nMargin & mghtRight)
		{
			if (rect.right > m_rect.right)  rect.right = m_rect.right;
			if (rect.right < m_rect.left)  rect.right = m_rect.left;
			if (rect.right < rect.left)  rect.left = rect.right;
		}
		else if (nMargin & mghtLeft)
		{
			if (rect.left < m_rect.left)  rect.left = m_rect.left;
			if (rect.left > m_rect.right)  rect.left = m_rect.right;
			if (rect.left > rect.right)  rect.right = rect.left;
		}
		if (nMargin & mghtBottom)
		{
			if (rect.bottom > m_rect.bottom)  rect.bottom = m_rect.bottom;
			if (rect.bottom < m_rect.top)  rect.bottom = m_rect.top;
			if (rect.bottom < rect.top)  rect.top = rect.bottom;
		}
		else if (nMargin & mghtTop)
		{
			if (rect.top < m_rect.top)  rect.top = m_rect.top;
			if (rect.top > m_rect.bottom)  rect.top = m_rect.bottom;
			if (rect.top > rect.bottom)  rect.bottom = rect.top;
		}
		m_margin.left = rect.left - m_rect.left;
		m_margin.right = m_rect.right - rect.right;
		m_margin.top = rect.top - m_rect.top;
		m_margin.bottom = m_rect.bottom - rect.bottom;
	}

	virtual void AdjustMargin(int nMargin, POINT point)
	{
		RECT rect;
		GetCenterRect(rect, m_margin, m_rect);
		ASSERT(nMargin & mghtMask);
		if (nMargin & mghtRight)
		{
			rect.right = point.x;
			if (rect.right > m_rect.right)  rect.right = m_rect.right;
			if (rect.right < m_rect.left)  rect.right = m_rect.left;
			if (rect.right < rect.left)  rect.left = rect.right;
		}
		else if (nMargin & mghtLeft)
		{
			rect.left = point.x;
			if (rect.left < m_rect.left)  rect.left = m_rect.left;
			if (rect.left > m_rect.right)  rect.left = m_rect.right;
			if (rect.left > rect.right)  rect.right = rect.left;
		}
		if (nMargin & mghtBottom)
		{
			rect.bottom = point.y;
			if (rect.bottom > m_rect.bottom)  rect.bottom = m_rect.bottom;
			if (rect.bottom < m_rect.top)  rect.bottom = m_rect.top;
			if (rect.bottom < rect.top)  rect.top = rect.bottom;
		}
		else if (nMargin & mghtTop)
		{
			rect.top = point.y;
			if (rect.top < m_rect.top)  rect.top = m_rect.top;
			if (rect.top > m_rect.bottom)  rect.top = m_rect.bottom;
			if (rect.top > rect.bottom)  rect.bottom = rect.top;
		}
		m_margin.left = rect.left - m_rect.left;
		m_margin.right = m_rect.right - rect.right;
		m_margin.top = rect.top - m_rect.top;
		m_margin.bottom = m_rect.bottom - rect.bottom;
	}

	void UpdateLastBoxes()
	{
		RECT rect;
		GetCenterRect(rect, m_margin, m_rect);

		m_boxLast[0].left = rect.right;
		m_boxLast[0].right = rect.right + 1;
		m_boxLast[0].top = m_rect.top;
		m_boxLast[0].bottom = m_rect.bottom;

		m_boxLast[1].left = rect.left - 1;
		m_boxLast[1].right = rect.left;
		m_boxLast[1].top = m_rect.top;
		m_boxLast[1].bottom = m_rect.bottom;

		m_boxLast[2].left = m_rect.left;
		m_boxLast[2].right = m_rect.right;
		m_boxLast[2].top = rect.bottom;
		m_boxLast[2].bottom = rect.bottom + 1;

		m_boxLast[3].left = m_rect.left;
		m_boxLast[3].right = m_rect.right;
		m_boxLast[3].top = rect.top - 1;
		m_boxLast[3].bottom = rect.top;
	}

	int HitTest(CPoint point)
	{
		int nMargin = HitTestMargins(point);
		if (nMargin & mghtMask)
			return nMargin;
		if (!m_bCanTrackRect)
			return hitNothing;
		int nHandle = HitTestHandles(point);
		if ((unsigned)nHandle <= 8)
			return nHandle;
		return hitNothing;
	}

	int HitTestMargins(CPoint point)
	{
		if (!m_rect.PtInRect(point))
			return 0;
		int ht = 0;
		RECT rect;
		GetCenterRect(rect, m_margin, m_rect);
		{
			if (point.x >= rect.left - m_nHandleSize && point.x <= rect.left)
				ht |= mghtLeft;
			else if (point.x >= rect.right && point.x <= rect.right + m_nHandleSize)
				ht |= mghtRight;
			else if (point.x >= rect.left && point.x <= rect.left + m_nHandleSize)
				ht |= mghtLeft;
			else if (point.x >= rect.right - m_nHandleSize && point.x <= rect.right)
				ht |= mghtRight;
		}
		{
			if (point.y >= rect.top - m_nHandleSize && point.y <= rect.top)
				ht |= mghtTop;
			else if (point.y >= rect.bottom && point.y <= rect.bottom + m_nHandleSize)
				ht |= mghtBottom;
			else if (point.y >= rect.top && point.y <= rect.top + m_nHandleSize)
				ht |= mghtTop;
			else if (point.y >= rect.bottom - m_nHandleSize && point.y <= rect.bottom)
				ht |= mghtBottom;
		}
		return ht;
	}

	BOOL SetCursor(CWnd* pWnd, UINT nHitTest)
	{
		if (nHitTest != HTCLIENT)
			return FALSE;
		POINT point;
		GetCursorPos(&point);
		pWnd->ScreenToClient(&point);
		int nMargin = HitTestMargins(point);
		if (SetMarginCursor(pWnd, nMargin))
			return TRUE;
		if (!m_bCanTrackRect)
			return FALSE;
		return CRectTracker::SetCursor(pWnd, nHitTest);
	}

	BOOL SetMarginCursor(CWnd* pWnd, int nMargin)
	{
		static HCURSOR m_cursorEW = ::LoadCursor(NULL, IDC_SIZEWE);
		static HCURSOR m_cursorNS = ::LoadCursor(NULL, IDC_SIZENS);
		static HCURSOR m_cursorNWSE = ::LoadCursor(NULL, IDC_SIZENWSE);
		static HCURSOR m_cursorNESW = ::LoadCursor(NULL, IDC_SIZENESW);
		switch (nMargin)
		{
		case mghtLeft:
		case mghtRight:
			::SetCursor(m_cursorEW);
			return TRUE;
		case mghtTop:
		case mghtBottom:
			::SetCursor(m_cursorNS);
			return TRUE;
		case mghtTopLeft:
		case mghtBottomRight:
			::SetCursor(m_cursorNWSE);
			return TRUE;
		case mghtTopRight:
		case mghtBottomLeft:
			::SetCursor(m_cursorNESW);
			return TRUE;
		default: return FALSE;
		}
		return FALSE;
	}

	BOOL Track(CWnd* pWnd, CPoint point, BOOL /*bAllowInvert*/ = FALSE, CWnd* pWndClipTo = NULL)
	{
		int nMargin = HitTestMargins(point);
		if (nMargin & mghtMask)
			return TrackMargin(nMargin, pWnd, point, pWndClipTo);
		else if (m_bCanTrackRect)
		{
			m_bAllowInvert = FALSE;
			int nHandle = HitTestHandles(point);
			if ((unsigned)nHandle > 8)
				return FALSE;
			return TrackHandle(nHandle, pWnd, point, pWndClipTo);
		}
		return FALSE;
	}

	BOOL TrackMargin(int nMargin, CWnd* pWnd, CPoint point, CWnd* pWndClipTo)
	{
		ASSERT(nMargin & mghtMask);

		// don't handle if capture already set
		if (::GetCapture() != NULL)
			return FALSE;

		AfxLockTempMaps();  // protect maps while looping

		ASSERT(!m_bFinalErase);

		// set capture to the window which received this message
		pWnd->SetCapture();
		ASSERT(pWnd == CWnd::GetCapture());
		pWnd->UpdateWindow();
		if (pWndClipTo != NULL)
			pWndClipTo->UpdateWindow();

		LOSHUMARGIN marginSave = m_margin;
		UpdateLastBoxes();

		// get DC for drawing
		CDC* pDrawDC;
		if (pWndClipTo != NULL)
		{
			// clip to arbitrary window by using adjusted Window DC
			pDrawDC = pWndClipTo->GetDCEx(NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE);
		}
		else
		{
			// otherwise, just use normal DC
			pDrawDC = pWnd->GetDC();
		}
		ENSURE_VALID(pDrawDC);

		__try 
		{
			// get messages until capture lost or canceled/accepted
			for (;;)
			{
				MSG msg;
				VERIFY(::GetMessage(&msg, NULL, 0, 0));

				if (CWnd::GetCapture() != pWnd)
					break;

				switch (msg.message)
				{
					// handle accept message
				case WM_LBUTTONUP:
					// flow down to deal as WM_MOUSEMOVE
					// handle movement message
				case WM_MOUSEMOVE:
					pWnd->ScreenToClient(&msg.pt);
					AdjustMargin(nMargin, msg.pt);
					pWnd->InvalidateRect(&m_boxLast[0]);
					pWnd->InvalidateRect(&m_boxLast[1]);
					pWnd->InvalidateRect(&m_boxLast[2]);
					pWnd->InvalidateRect(&m_boxLast[3]);
					UpdateLastBoxes();
					pWnd->InvalidateRect(&m_boxLast[0]);
					pWnd->InvalidateRect(&m_boxLast[1]);
					pWnd->InvalidateRect(&m_boxLast[2]);
					pWnd->InvalidateRect(&m_boxLast[3]);
					if (WM_LBUTTONUP == msg.message)
					{
						m_bFinalErase = TRUE;
						return TRUE;
					}
					break;

					// handle cancel messages
				case WM_KEYDOWN:
					if (msg.wParam != VK_ESCAPE)
						break;
					//else flow down to deal as WM_RBUTTONDOWN
				case WM_RBUTTONDOWN:
					m_margin = marginSave;
					return FALSE;
					break;

					// just dispatch rest of the messages
				default:
					DispatchMessage(&msg);
					break;
				}
			}
			return TRUE;
		}
		__finally
		{
			if (pWndClipTo != NULL)
				pWndClipTo->ReleaseDC(pDrawDC);
			else
				pWnd->ReleaseDC(pDrawDC);
			ReleaseCapture();

			AfxUnlockTempMaps(FALSE);

			m_bFinalErase = FALSE;
			m_bErase = FALSE;
		}
	}

};

#endif //__CR_LOSHU_TRACKER_H__